#include <iostream>
#include <cstdlib>
#include <fstream>
#include <string>
#include <cstring>
#include "timetools.h"
#include <errno.h>

#include "arglib.h"



using namespace std;


std::vector<double*> matricesA;
std::vector<double*> matricesB;
std::vector<double*> matricesC;

clarg::argInt nmats("-nm", "Number of matrix multiplications", 1);
clarg::argInt matsz("-msz", "Matrix size = N x N", 1000);
clarg::argInt nthreads("-nt", "Number of threads", 1);

int next_matrix = 0;


double* new_matrix(double* src, unsigned N) {
	double* m = (double*) malloc(N*N*sizeof(double));
	
	if (src != NULL)
		memcpy( m, src, N*N*sizeof(double) );
	else
		memset( m, 0, N*N*sizeof(double) );
	return m;
}


void init_matrices() {
	for (unsigned i=0; i<nmats.get_value(); i++) {
		matricesA.push_back( new_matrix(0, matsz.get_value()) );
		matricesB.push_back( new_matrix(0, matsz.get_value()) );
		matricesC.push_back( new_matrix(0, matsz.get_value()) );
	}
}


void free_matrices() {
	for (unsigned i=0; i<nmats.get_value(); i++) {
		free(matricesA[i]);
		free(matricesB[i]);
		free(matricesC[i]);
	}
}

int get_matrix()
{
	int id; 

	if (next_matrix >= nmats.get_value())
		return -1;

	id = next_matrix++;

	return id;
}


void
__attribute__((noinline))
process(double * A, double * B, double * C, unsigned N)
{
    for (unsigned i=0; i<N; i++) {
        for (unsigned j=0; j<N; j++) {
            double tmp=0;
            for (unsigned k=0; k<N; k++)
                tmp += A[i*N + k] * B[k*N + j];

            C[i*N + j] = tmp;
        }
    }
}

void* worker_thread() {
	int matrix_id;
	
	while ( (matrix_id = get_matrix()) >= 0 ) {
		double* A = matricesA[matrix_id];
		double* B = matricesB[matrix_id];
		double* C = matricesC[matrix_id];
		unsigned N = matsz.get_value();

		process(A, B, C, N);

	} //while ( (matrix_id = get_matrix()) >= 0 )
	
	return NULL;
}


int main(int argc, char *argv[]) {

	if (clarg::parse_arguments(argc, argv)) {
		cerr << "Error when parsing the arguments!" << endl;
		return 1;
	}

	if (nmats.get_value() < 1) {
		cerr << "Error, nm must be >= 1" << endl;
		return 1;
	}

	init_matrices();

	int n_threads = nthreads.get_value();
	std::vector<pthread_t> threads(n_threads);
#ifdef ENABLE_OUTPUT
	 struct timespec startTime, endTime;

    readTime(&startTime);
#endif
        worker_thread();
#ifdef ENABLE_OUTPUT
    readTime(&endTime);

    const double elapsedTime = timeDiff(startTime, endTime);

    printf("SIZE %d\nTIME %f\n", matsz.get_value(), elapsedTime);
#endif
	free_matrices();
	
	return 0;
}
